package Net;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.concurrent.ConcurrentHashMap;


import Interfaces.EventLoopAcceptCallback;
import Interfaces.EventLoopReadCallback;
import Interfaces.TcpServerOnCloseConnectionCallback;
import Interfaces.TcpServerOnConnectionCallback;
import Interfaces.TcpServerOnReadCallback;
import Util.ApiException;
import Util.Config;

public class TcpServer implements EventLoopReadCallback, EventLoopAcceptCallback{

	private ServerSocket socket_;
	private ServerSocketChannel channel_;
	private int port_;
	private EventLoop loop_;
	private ConcurrentHashMap<String, TcpConnection> connectionMap_;
	
	private TcpServerOnConnectionCallback connectionCb_;
	private TcpServerOnReadCallback readCb_;
	private TcpServerOnCloseConnectionCallback closeCb_;
	
	public void setConnectionCallback(TcpServerOnConnectionCallback cb)
	{
		connectionCb_ = cb;
	}
	
	public void setReadCallback(TcpServerOnReadCallback cb)
	{
		readCb_ = cb;
	}
	
	public void setCloseCallback(TcpServerOnCloseConnectionCallback cb)
	{
		closeCb_ = cb;
	}
	@Override
	public void onAccept(SelectionKey key) throws IOException {
		// TODO Auto-generated method stub
		Config.logger.info("onAccept");
		ServerSocketChannel server = (ServerSocketChannel) key.channel();
		SocketChannel channel = server.accept();
		channel.configureBlocking(false);
		channel.register(loop_.getSelector(), SelectionKey.OP_READ);
		//need to make a connection
		TcpConnection connection = new TcpConnection(this,channel);
		connectionMap_.putIfAbsent(channel.toString(), connection);
		//invoke the call back
		if (connectionCb_!=null)
			connectionCb_.OnConnectionCallback(channel.toString());
	}

	@Override
	public void onRead(SelectionKey key) throws IOException, ApiException {
		// TODO Auto-generated method stub
		Config.logger.info("onRead");
		SocketChannel channel = (SocketChannel) key.channel();
		TcpConnection connection = connectionMap_.get(channel.toString());
		int len = 0;
		if (connection!=null)
		{
			len =connection.onReadMessage();
			if (readCb_!=null && len!=-1)
				readCb_.onReadCallback(connection.getCid(), connection.getBuffer(), len);
		}else 
		{
			Config.logger.info("invalid read connection");
			throw new ApiException("invalid read connection");
		}
	}
	
	public int onSendMessage(String cid, ByteBuffer buffer) throws IOException
	{
		TcpConnection connection = connectionMap_.get(cid);
		int len = 0;
		if (connection!=null)
		{
			len = connection.onSendMessage(buffer);
		}else
		{
			Config.logger.info("invalid sendMessage connection");
		}
		return len;
	}
	
	public TcpServer(EventLoop loop,int port)
	{
		port_ = port;
		loop_ = loop;
		connectionMap_ = new ConcurrentHashMap<String, TcpConnection>(); 
	}
	
	public void listen() throws IOException
	{
		channel_ = ServerSocketChannel.open();
		socket_ = channel_.socket();
		socket_.bind(new InetSocketAddress(port_));
		channel_.configureBlocking(false);
		channel_.register(loop_.getSelector(), SelectionKey.OP_ACCEPT);
		loop_.setAcceptCb(this);
		loop_.setReadCb(this);
	}
	
	public void connectionIsClose(String cid)
	{
		connectionMap_.remove(cid);
		closeCb_.OnCloseConnection(cid);
	}
	
}
